{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Generics in Python\n",
        "\n",
        "Generics allow us to define functions and classes that can operate on different data types while maintaining type safety.\n"
      ],
      "metadata": {
        "id": "HKUbmpcH13ae"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 1. Introduction to Generics\n",
        "---------------------------"
      ],
      "metadata": {
        "id": "SFjTVZoQ1_Ph"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Example without Generics\n",
        "def first_element(items):\n",
        "    return items[0]\n",
        "\n",
        "nums = [1, 2, 3]\n",
        "strings = ['a', 'b', 'c']\n",
        "\n",
        "print(first_element(nums))     # 1\n",
        "print(first_element(strings))  # 'a'\n",
        "\n",
        "# Issue: No type checking. We can't restrict or inform about expected data types explicitly."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "wKgoHBme2C6s",
        "outputId": "0e60f7a5-0fbf-488c-8220-b4213ee0d94d"
      },
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1\n",
            "a\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Why Use Generics When Python Lets You Pass Any List?\n",
        "\n",
        "1. **Static Type Checking**  \n",
        "   - **Without generics**, you can pass any list, but static type checkers (like [mypy](http://mypy-lang.org/)) cannot verify that your function is used correctly. For instance, if your function is meant to handle only strings but you accidentally pass a list of integers, Python won't complain until (or unless) something goes wrong at runtime.  \n",
        "   - **With generics**, you declare something like `List[str]` or `List[int]`, and a type checker will immediately flag if you pass the wrong type. This early feedback catches type errors before they become runtime bugs.\n",
        "\n",
        "2. **Code Clarity and Intent**  \n",
        "   - Generics communicate clearly to other developers (and future you) that `first_element(items: List[T]) -> T` is intended to work with a list of a single, consistent type `T`.  \n",
        "   - When you see `List[str]`, there is no ambiguity about what the list is supposed to contain. This helps prevent accidental mixing of data types.\n",
        "\n",
        "3. **Improved Tooling Support**  \n",
        "   - Modern IDEs can use your generic annotations to provide better **autocompletion, refactoring,** and **linting** suggestions.  \n",
        "   - For example, if a function returns `T`, your IDE will automatically know the returned type is `str` for a `List[str]`, saving time when using the result elsewhere in your code.\n",
        "\n",
        "4. **Future-Proofing**  \n",
        "   - As projects grow more complex and data structures become nested, generics help keep track of types. This is especially crucial in large-scale applications like **production AI systems**, where data consistency and correctness are paramount.\n",
        "\n",
        "5. **Avoiding Silent Logic Errors**  \n",
        "   - Without generics, a developer could pass any list, perhaps by mistake. You might not catch it until it causes a subtle bug (like a `TypeError` in production).  \n",
        "   - By declaring generic types, the mismatch is caught early, which often saves hours of debugging.\n",
        "\n",
        "---\n",
        "\n",
        "In short, Python’s flexibility of “pass any list” is convenient for small scripts or quick prototypes. However, in larger, more complex, or production-grade systems—especially with AI or data-heavy workflows—generics, combined with type checkers, dramatically improve reliability, clarity, and maintainability."
      ],
      "metadata": {
        "id": "Eh7m3VCC3Eis"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 2. Using Generics\n",
        "------------------"
      ],
      "metadata": {
        "id": "LFg202nJ2Ld_"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from typing import TypeVar, List\n",
        "\n",
        "# Type variable for generic typing\n",
        "T = TypeVar('T')\n",
        "\n",
        "def generic_first_element(items: List[T]) -> T:\n",
        "    return items[0]\n",
        "\n",
        "num_result = generic_first_element(nums)        # type inferred as int\n",
        "string_result = generic_first_element(strings)  # type inferred as str\n",
        "\n",
        "print(num_result)    # 1\n",
        "print(string_result) # 'a'"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "XN8TRS1l2FJi",
        "outputId": "f00866d3-2e79-496e-e4a9-235fbad9c949"
      },
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1\n",
            "a\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Explanation: By using Generics, Python can infer and enforce types at compile-time, enhancing clarity and safety.\n"
      ],
      "metadata": {
        "id": "SABi7odk2UKi"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 3. Generic Classes\n",
        "-------------------"
      ],
      "metadata": {
        "id": "VUZEK-qz2Vho"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from typing import Generic, TypeVar, ClassVar\n",
        "from dataclasses import dataclass, field\n",
        "\n",
        "# Type variable for generic typing\n",
        "T = TypeVar('T')\n",
        "\n",
        "@dataclass\n",
        "class Stack(Generic[T]):\n",
        "    items: List[T] = field(default_factory=list)\n",
        "    limit: ClassVar[int] = 10\n",
        "\n",
        "    def push(self, item: T) -> None:\n",
        "        self.items.append(item)\n",
        "\n",
        "    def pop(self) -> T:\n",
        "        return self.items.pop()\n",
        "\n",
        "stack_of_ints = Stack[int]()\n",
        "print(stack_of_ints)\n",
        "print(stack_of_ints.limit)\n",
        "stack_of_ints.push(10)\n",
        "stack_of_ints.push(20)\n",
        "\n",
        "print(stack_of_ints.pop())  # 20\n",
        "\n",
        "stack_of_strings = Stack[str]()\n",
        "print(stack_of_strings)\n",
        "stack_of_strings.push(\"hello\")\n",
        "stack_of_strings.push(\"world\")\n",
        "\n",
        "print(stack_of_strings.pop())  # 'world'"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6t8pQJjh2TIc",
        "outputId": "91ccff81-0d5b-494e-8bfd-34a354c41fcc"
      },
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Stack(items=[])\n",
            "10\n",
            "20\n",
            "Stack(items=[])\n",
            "world\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "print(Stack.limit)\n",
        "print(stack_of_ints.limit)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f2DneW-l4WYy",
        "outputId": "96555754-342c-45b7-b37a-8d30c5218534"
      },
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "10\n",
            "10\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Generic classes like Stack[T] allow you to define data structures that maintain consistent types, improving type safety.\n"
      ],
      "metadata": {
        "id": "Oaa1gvH92aL2"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 4. Advanced Usage of Generics\n",
        "-----------------------------\n",
        "\n",
        "Using Generics with multiple TypeVars"
      ],
      "metadata": {
        "id": "wQsxe5bU2mYT"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {
        "id": "nFZPu1-D1E2p"
      },
      "outputs": [],
      "source": [
        "K = TypeVar('K')\n",
        "V = TypeVar('V')\n",
        "\n",
        "# Incorrect Usage (without Generic inheritance)\n",
        "from dataclasses import dataclass\n",
        "@dataclass\n",
        "class KeyValuePair:\n",
        "    key: K\n",
        "    value: V\n",
        "# This snippet incorrectly attempts generics without inheriting from Generic, causing static type checkers to complain.\n"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Correct Usage (with Generic inheritance)\n",
        "@dataclass\n",
        "class CorrectKeyValuePair(Generic[K, V]):\n",
        "    key: K\n",
        "    value: V\n",
        "\n",
        "pair = CorrectKeyValuePair(\"age\", 30)\n",
        "\n",
        "print(pair.key)    # 'age'\n",
        "print(pair.value)  # 30\n",
        "\n",
        "# Explanation of Differences:\n",
        "# - Without Generic inheritance: TypeVars K, V are unbound, causing static checkers to fail.\n",
        "# - With Generic inheritance: Explicitly informs type checkers, ensuring accurate type inference and improved static checking."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "FIhBWkj12tIZ",
        "outputId": "5c3cecc8-9ece-46e4-9c91-aee0d0e94d3a"
      },
      "execution_count": 30,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "age\n",
            "30\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 5. Practical Example with Generics"
      ],
      "metadata": {
        "id": "v4h9XHWW5oRn"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## a. Generic function that merges two dictionaries"
      ],
      "metadata": {
        "id": "9hswlotY5sCN"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "def merge_dicts(dict1: dict[K, V], dict2: dict[K, V]) -> dict[K, V]:\n",
        "    result = dict1.copy()\n",
        "    result.update(dict2)\n",
        "    return result\n",
        "\n",
        "merged = merge_dicts({'a': 1}, {'b': 2})\n",
        "print(merged)  # {'a': 1, 'b': 2}"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "jwIO2PgZ2wBX",
        "outputId": "d69a31b8-ceed-4fde-f777-5fd90bda93a4"
      },
      "execution_count": 31,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'a': 1, 'b': 2}\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## b. Generics with DataClasses\n",
        "----------------------------"
      ],
      "metadata": {
        "id": "CGaYnyu95ucG"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Dataclasses combined with Generics enhance clarity, immutability, and type safety for complex data structures.\n",
        "\n",
        "@dataclass\n",
        "class GenericDataContainer(Generic[T]):\n",
        "    data: T\n",
        "\n",
        "int_container = GenericDataContainer[int](data=123)\n",
        "str_container = GenericDataContainer[str](data=\"Generics in Python\")\n",
        "\n",
        "print(int_container.data)  # 123\n",
        "print(str_container.data)  # 'Generics in Python'"
      ],
      "metadata": {
        "id": "dGoivMcW5yqv"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "\n",
        "# Production Grade Example for AI Agents\n",
        "@dataclass\n",
        "class AgentState(Generic[K, V]):\n",
        "    context: dict[K, V]\n",
        "    status: str\n",
        "\n",
        "agent_state = AgentState[str, str](context={\"task\": \"data collection\", \"priority\": \"high\"}, status=\"active\")\n",
        "\n",
        "print(agent_state.context)  # {'task': 'data collection', 'priority': 'high'}\n",
        "print(agent_state.status)   # 'active'"
      ],
      "metadata": {
        "id": "2k4A_XDN59fY",
        "outputId": "d2f53c73-5b60-448f-caec-aba7b385e853",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "execution_count": 32,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'task': 'data collection', 'priority': 'high'}\n",
            "active\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Summary"
      ],
      "metadata": {
        "id": "72a_-h1-2zbi"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Always explicitly inherit from Generic when using TypeVar in Python classes to clearly communicate intentions to static type checkers and to avoid subtle type-related bugs.\n",
        "\n",
        "Generics significantly enhance type safety, readability, and maintainability, making them critical for robust, scalable, and production-grade AI agent systems.\n"
      ],
      "metadata": {
        "id": "7tCTmRLm2xWA"
      }
    }
  ]
}